package demo.security;


import java.io.IOException;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
	
	@Value("${demo.security.enabled}")
	boolean isSecurityEnabled;
	
	@Bean("MyOAuth2RestTemplate")
	@LoadBalanced
	public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext, OAuth2ProtectedResourceDetails details) {
	    return new OAuth2RestTemplate(details, oauth2ClientContext);
	}
	
	@Configuration
	@EnableWebSecurity
	@EnableGlobalMethodSecurity(prePostEnabled = true)
	static class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
		@Override
		protected void configure(HttpSecurity http) throws Exception {
			log.info("****Override WebSecurityConfigurerAdapter.configure(HttpSecurity) to DO NOTHING! Use ResourceServerConfigurerAdapter.configure(HttpSecurity)");
		}		
	}
	
	@Override
	public void configure(HttpSecurity http) throws Exception {
		
		log.info("**********ResourceServerConfiguration:configure(HttpSecurity http)**********isSecurityEnabled={}",isSecurityEnabled);
		
		if(isSecurityEnabled){
			log.info("**********authenticate /customer/security/** request**********");	
			
			http.authorizeRequests()
				.antMatchers("/customer/greeting/**").permitAll()
				.antMatchers("/customer/**").authenticated()				
				.anyRequest().permitAll();							
		}
		else
		{				
			log.info("**********any request anonymous**********");				
			http.authorizeRequests().anyRequest().permitAll();	
		}	
	}
	
	@Configuration
	@Conditional(JwtTokenStoreCondition.class)
	static class JwtTokenConfiguration {
		
		public JwtTokenConfiguration() {
			log.info("**********JwtTokenConfiguration**********");
		}
		
		@Bean
		@ConditionalOnMissingBean(ResourceServerTokenServices.class)
		public DefaultTokenServices jwtTokenServices(TokenStore jwtTokenStore) {
			log.info("**********jwtTokenServices**********");
			DefaultTokenServices services = new DefaultTokenServices();
			services.setTokenStore(jwtTokenStore);
			return services;
		}
		

		@Bean
		@ConditionalOnMissingBean(TokenStore.class)
		public TokenStore jwtTokenStore() {
			log.info("**********jwtTokenStore**********");
			return new JwtTokenStore(overrideDefaultJwtTokenEnhancer());
		}
		

		@Bean
		@Primary
		public JwtAccessTokenConverter overrideDefaultJwtTokenEnhancer() {
			
			log.info("********************@Primary JwtAccessTokenConverter ******************");
			
			Resource resource = new ClassPathResource("public.cert");
			
			JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
					
			try {			
				String publicKey = new String(IOUtils.toByteArray(resource.getInputStream()));
				converter.setVerifierKey(publicKey);
			}
			catch(IOException e) {
				throw new RuntimeException("Failed to read public.cert", e);
			}
			
			return converter;
		}		
	}
	
	static class JwtTokenStoreCondition extends SpringBootCondition {

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context,
				AnnotatedTypeMetadata metadata) {

			ConditionMessage.Builder message = ConditionMessage.forCondition("tokenStore Condition");
			Environment environment = context.getEnvironment();
			String tokenStore = environment.getProperty("demo.security.tokenStore");
			if ("JWT".equals(tokenStore)) {
				return ConditionOutcome.match(message.foundExactly("JWT tokenStore"));
			}
			
			return ConditionOutcome.noMatch(message.didNotFind("demo.security.tokenStore").atAll());
		}
	}	
}
